iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0

昨天我們看過了 val customer = call.receive<Customer>() 這段函數,在 Ktor 裡面是怎麼將 HTML 請求轉換成自定義的 Customer 物件。

今天我們來看看,在處理回傳的時候,Ktor 又是如何將自定義物件轉換成 HTML 回傳的。

在 Ktor 的教學裡面,如果我們要試著取出 Customer 並回傳,寫法如下

get("{id?}") {
	val id = call.parameters["id"] ?: return@get call.respondText(
		"Missing id",
		status = HttpStatusCode.BadRequest
	)
	val customer =
		customerStorage.find { it.id == id } ?: return@get call.respondText(
			"No customer with id $id",
			status = HttpStatusCode.NotFound
		)
	call.respond(customer)
}

前面先透過 call.parameters["id"] 取得 id,我們來看看 parameters 的實作

/**
 * Parameters associated with this call.
 */
public val parameters: Parameters
/**
 * Represents HTTP parameters as a map from case-insensitive names to collection of [String] values
 */
public interface Parameters : StringValues {
    public companion object {
        /**
         * Empty [Parameters] instance
         */
        public val Empty: Parameters = EmptyParameters

        /**
         * Builds a [Parameters] instance with the given [builder] function
         * @param builder specifies a function to build a map
         */
        public inline fun build(builder: ParametersBuilder.() -> Unit): Parameters =
            ParametersBuilder().apply(builder).build()
    }
}

這邊我們看到使用了 ParametersBuilder 來進行 parameters 的處理

至於 respond 的實作則是如下

/**
 * Sends a [message] as a response.
 * @see [io.ktor.server.response.ApplicationResponse]
 */
@OptIn(InternalAPI::class)
@JvmName("respondWithType")
public suspend inline fun <reified T : Any> ApplicationCall.respond(message: T) {
    if (message !is OutgoingContent && message !is ByteArray) {
        response.responseType = typeInfo<T>()
    }
    response.pipeline.execute(this, message as Any)
}

前面先將 typeInfo 設置成了輸入的 T ,在我們這邊的範例來說,就是 Customer 類別

這邊的 response.pipeline.execute(this, message as Any)

跟昨天我們看到的 val transformed = request.pipeline.execute(this, incomingContent) 似乎有點相近

我們分別來看一下這兩段程式

昨天的 request.pipeline

/**
 * A pipeline for receiving content.
 */
public val pipeline: ApplicationReceivePipeline
public open class ApplicationReceivePipeline(
    override val developmentMode: Boolean = false
) : Pipeline<Any, ApplicationCall>(Before, Transform, After)

今天的 response.pipeline 則是

/**
 * A pipeline for sending content.
 */
public val pipeline: ApplicationSendPipeline
/**
 * Server response send pipeline.
 */
public open class ApplicationSendPipeline(
    override val developmentMode: Boolean = false
) : Pipeline<Any, ApplicationCall>(Before, Transform, Render, ContentEncoding, TransferEncoding, After, Engine)

到這邊,我們可以發現,這裡共同繼承了 Pipeline 類別,所以不需要分別撰寫階段處理請求或者回傳的邏輯。我們就可以用類似的寫法,來將請求轉換成程式自己定義的類別,或者將自己定義的類別轉換成回傳。

另外,透過這樣撰寫的方式,我們可以在不需要特別撰寫新程式的狀態下,讓套件可以參與轉換請求或者轉換回傳的邏輯。

這樣一來,如果我們希望回傳的格式不一樣,比方說我們想改成回傳 xml

只需要在安裝對應套件後,這樣調整程式

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        xml()
    }
}

裡面就會改成

/**
 * Registers the `application/xml` (or another specified [contentType]) content type
 * to the [ContentNegotiation] plugin using kotlinx.serialization.
 *
 * You can learn more from [Content negotiation and serialization](https://ktor.io/docs/serialization.html).
 *
 * @param format instance. [DefaultXml] is used by default
 * @param contentType to register with, `application/xml` by default
 */
public fun Configuration.xml(
    format: XML = DefaultXml,
    contentType: ContentType = ContentType.Application.Xml
) {
    serialization(contentType, format)
}

然後取得 XML 物件

/**
 * The default XML configuration. The settings are:
 * - Every declaration without a namespace is automatically wrapped in the namespace.
 *       See also [XMLOutputFactory.IS_REPAIRING_NAMESPACES].
 * - The XML declaration is not generated.
 * - The indent is empty.
 * - Polymorphic serialization is disabled.
 *
 * See [XML] for more details.
 */
public val DefaultXml: XML = XML {
    repairNamespaces = true
    xmlDeclMode = XmlDeclMode.None
    indentString = ""
    autoPolymorphic = false
    this.xmlDeclMode
}
@OptIn(ExperimentalSerializationApi::class, ExperimentalXmlUtilApi::class)
public class XML constructor(
    public val config: XmlConfig,
    serializersModule: SerializersModule = EmptySerializersModule()
) : StringFormat 

然後成功的將請求與回傳改成用 XML 的形式進行處理。

到今天,我們看過了 Ktor 如何安裝 ContentNegotiation ,並可以自由地調整解析的設定

讓我們可以將自定義的類別,與 HTML 請求和回傳,自由的進行轉換。

這半個月希望看的人都有一些收穫,今天我們就先看到這邊。


上一篇
Day 14:call.receive 如何將請求轉換成自定義類別
下一篇
Day 16:存取第三方 API,HttpClient 和 client.get
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言